﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using LaughingDog.GameObjects;
using LaughingDog.Managers;

namespace LaughingDog.Components
{
    /// <summary>
    /// A Transform object is used to store the position, rotation and scale of a GameObject.
    /// </summary>
    public class Transform : Component
    {
        private Vector2 mPosition = Vector2.Zero;
        private Vector2 mLocalPosition = Vector2.Zero;

        private Single mRotation = 0.0f;
        private Single mLocalRotation = 0.0f;

        private Vector2 mSize = Vector2.One;        

        private Vector2 mScale = Vector2.One;
        private Vector2 mLocalScale = Vector2.Zero;

        private Transform mParent = null;        
        private List<Transform> mChildren = new List<Transform>();

        private Vector2 mVelocity = Vector2.Zero;
        private Vector2 mAcceleration = Vector2.Zero;        

        /// <summary>
        /// The world position of the Transform.
        /// </summary>
        public Vector2 Position { get { return mPosition; } set { SetPosition(value); } }

        /// <summary>
        /// The world X position of the Transform.
        /// </summary>
        public Single X { get { return Position.X; } set { SetPosition(new Vector2(value, Position.Y)); } }

        /// <summary>
        /// The world Y position of the Transform.
        /// </summary>
        public Single Y { get { return Position.Y; } set { SetPosition(new Vector2(Position.X, value)); } }

        /// <summary>
        /// Position of this Transform relative to its parent Transform.
        /// </summary>
        public Vector2 LocalPosition { get { if (mParent == null) return mPosition; else return mLocalPosition; } set { mLocalPosition = value; } }

        /// <summary>
        /// X Position of this Transform relative to its parent Transform.
        /// </summary>
        public Single LocalX { get { return LocalPosition.X; } set { LocalPosition = new Vector2(value, LocalPosition.Y); } }

        /// <summary>
        /// Y Position of this Transform relative to its parent Transform.
        /// </summary>
        public Single LocalY { get { return LocalPosition.Y; } set { LocalPosition = new Vector2(LocalPosition.X, value); } }

        /// <summary>
        /// The rotation of this Transform in degrees.
        /// </summary>
        public Single Rotation { get { return MathHelper.ToDegrees(mRotation); } set { SetRotation(MathHelper.ToRadians(value)); } }

        /// <summary>
        /// The rotation of this Transform in degrees relative to its parent Transform.
        /// </summary>
        public Single LocalRotation { get { if (mParent == null) return MathHelper.ToDegrees(mRotation); else return MathHelper.ToDegrees(mLocalRotation); } set { mLocalRotation = MathHelper.ToRadians(value); } }

        /// <summary>
        /// The rotation of this Transform in radians.
        /// </summary>
        public Single RotationRadians { get { return mRotation; } set { SetRotation(value); } }

        /// <summary>
        /// The rotation of this Transform in radians relative to its parent Transform.
        /// </summary>
        public Single LocalRotationRadians { get { if (mParent == null) return mRotation; else return mLocalRotation; } set { mLocalRotation = value; } }

        /// <summary>
        /// Gets or sets the size of this Transform.
        /// </summary>
        public Vector2 Size { get { return mSize; } set { SetSize(value); } }

        /// <summary>
        /// Gets the size of this Transform after having its scale applied.
        /// </summary>
        public Vector2 ScaledSize { get { return mSize * GameObject.Transform.Scale; } }       

        /// <summary>
        /// The scale of this Transform.
        /// </summary>
        public Vector2 Scale { get { return mScale; } set { SetScale(value); } }

        /// <summary>
        /// The scale of this Transform relative to its parent Transform
        /// </summary>
        public Vector2 LocalScale { get { if (mParent == null) return mScale; else return mLocalScale; } set { mLocalScale = value; } }

        /// <summary>
        /// Gets the current bounding box of the Transform (not accounting for rotation)
        /// </summary>
        public Rectangle BoundingBox { get { return new Rectangle((Int32)Position.X - (Int32)ScaledSize.X / 2, (Int32)Position.Y - (Int32)ScaledSize.Y / 2, (Int32)ScaledSize.X, (Int32)ScaledSize.Y); } }

        //public Rectangle AxisAllignedBoundingBox { get { return CalculateBoundingRectangle(BoundingBox); } }

        /// <summary>
        /// A list of all children currently attached to this Transform.
        /// </summary>
        public List<Transform> Children { get { return mChildren; } }

        /// <summary>
        /// The velocity of the Transform.
        /// </summary>
        public Vector2 Velocity { get { return mVelocity; } set { mVelocity = value; } }

        /// <summary>
        /// The X velocity of the Transform.
        /// </summary>
        public Single VelocityX { get { return Velocity.X; } set { Velocity = new Vector2(value, Velocity.Y); } }

        /// <summary>
        /// The Y velocity of the Transform.
        /// </summary>
        public Single VelocityY { get { return Velocity.Y; } set { Velocity = new Vector2(Velocity.X, value); } }

        /// <summary>
        /// The acceleration of the Transform.
        /// </summary>
        public Vector2 Acceleration { get { return mAcceleration; } set { mAcceleration = value; } }

        /// <summary>
        /// The X acceleration of the Transform.
        /// </summary>
        public Single AccelerationX { get { return Acceleration.X; } set { Acceleration = new Vector2(value, Acceleration.Y); } }

        /// <summary>
        /// The Y acceleration of the Transform.
        /// </summary>
        public Single AccelerationY { get { return Acceleration.Y; } set { Acceleration = new Vector2(Acceleration.X, value); } }

        /// <summary>
        /// Get or set this Transforms parent.
        /// </summary>
        public Transform Parent { get { return mParent; } 
            set 
            {
                if (value != null)
                {
                    mParent = value;
                    mParent.AddChild(this);
                }
                else
                    if(mParent != null)
                        mParent.RemoveChild(this);                
            } 
        }

        /// <summary>
        /// The Transform at the root of the hierarchy.
        /// </summary>
        public Transform Root { get { return GetRootTransform(); } }

        /// <summary>
        /// The count of how many children are currently attached to this Transform.
        /// </summary>
        public Int32 ChildCount { get { return mChildren.Count; } }

        /// <summary>
        /// Creates a new instance of a Transform component.
        /// </summary>
        /// <param name="gameObject">The GameObject that this component should be attached to.</param>
        public Transform(GameObject gameObject)
            : base(gameObject)
        {
        }

        /// <summary>
        /// Updates this Transform.
        /// </summary>        
        public override void Update()
        {
            if (Parent != null)
            {
                Position = mParent.Position + LocalPosition;
                Rotation = mParent.Rotation + LocalRotation;
                Scale = mParent.Scale + mLocalScale;
            }

            VelocityX = AccelerationX * (Single)Math.Cos(RotationRadians - MathHelper.ToRadians(90));// *TimeManager.Instance.Delta;
            VelocityY = AccelerationY * (Single)Math.Sin(RotationRadians - MathHelper.ToRadians(90));// *TimeManager.Instance.Delta;

            Position += Velocity * TimeManager.Instance.Delta;

            base.Update();
        }

        /// <summary>
        /// Moves the Transform in the direction and distance of translation.
        /// </summary>
        /// <param name="translation">The translation amount.</param>
        public void Translate(Vector2 translation)
        {
            SetPosition(mPosition += translation);
        }

        /// <summary>
        /// Moves the Transform in the direction and distance of translation.
        /// </summary>
        /// <param name="xTranslation">The translation amount along the X axis.</param>
        /// <param name="yTranslation">The translation amount along the Y axis.</param>
        public void Translate(Single xTranslation, Single yTranslation)
        {
            Translate(new Vector2(xTranslation, yTranslation));            
        }
        
        /// <summary>
        /// Rotates the Transform by the passed in value in degrees.
        /// </summary>
        /// <param name="rotation">The amount to rotate.</param>
        public void Rotate(Single rotation)
        {
            RotateRadians(MathHelper.ToRadians(rotation));
        }

        /// <summary>
        /// Rotates the Transform by the passed in value in radians.
        /// </summary>
        /// <param name="rotation">The amount to rotate.</param>
        public void RotateRadians(Single rotation)
        {
            SetRotation(mRotation += rotation);                       
        }

        /// <summary>
        /// Rotates the Transform so that it faces the location vector.
        /// </summary>
        /// <param name="location">The target location.</param>
        public void LookAt(Vector2 location)
        {
            Single x = location.X - Position.X;
            Single y = location.Y - Position.Y;

            RotationRadians = (Single)Math.Atan2(y, x);
            RotationRadians += MathHelper.ToRadians(90);            
        }

        /// <summary>
        /// Rotates the Transform so that it faces away from the location vector.
        /// </summary>
        /// <param name="location">The target location.</param>
        public void LookAway(Vector2 location)
        {           
            throw new NotImplementedException();
        }

        /// <summary>
        /// Unparents all children from this Transform.
        /// </summary>
        public void DetachChildren()
        {
            foreach (Transform transform in new List<Transform>(mChildren))
                transform.DetachFromParent();
        }

        /// <summary>
        /// Unparents this Transform from its parent.
        /// </summary>
        public void DetachFromParent()
        {
            Parent = null;
        }

        /// <summary>
        /// Adds a child to this Transform.
        /// </summary>
        /// <param name="transform">The child Transform to add.</param>
        public void AddChild(Transform transform)
        {
            mChildren.Add(transform);
        }

        /// <summary>
        /// Removes a child from this Transform.
        /// </summary>
        /// <param name="transform">The child Transform to remove.</param>
        public void RemoveChild(Transform transform)
        {
            mChildren.Remove(transform);
        }

        /// <summary>
        /// Finds a child by name and returns it.
        /// </summary>
        /// <param name="name">The name of the child to look for.</param>
        /// <returns>The found child.</returns>
        public Transform Find(String name)
        {
            foreach (Transform transform in new List<Transform>(mChildren))
            {
                if (transform.Name.Equals(name))
                    return transform;
            }

            return null;
        }

        /// <summary>
        /// Is this Transform a direct child of another Transform.
        /// </summary>
        /// <param name="transform">The possible parent Transform.</param>
        /// <returns>True if this Transform is a direct child.</returns>
        public Boolean IsChildOf(Transform transform)
        {
            if (Parent != null)
                return Parent.Equals(transform);

            return false;
        }

        /// <summary>
        /// Sets the size of this Transform component.
        /// </summary>
        /// <param name="size">The size to set.</param>
        private void SetSize(Vector2 size)
        {
            mSize = size;

            //if (GameObject.RigidBody != null)            
              //  GameObject.RigidBody.Recreate();
            //if (GameObject.Collider != null)
            //    GameObject.Collider.Recreate();            
        }

        /// <summary>
        /// Sets the scale of this Transform component.
        /// </summary>
        /// <param name="scale">The scale to set.</param>
        private void SetScale(Vector2 scale)
        {
            mScale = scale;           
        }

        /// <summary>
        /// Sets the position of this Transform component.
        /// </summary>
        /// <param name="position">The position to set.</param>
        private void SetPosition(Vector2 position)
        {
            if (GameObject.RigidBody != null)
                GameObject.RigidBody.Body.Position = position;

            mPosition = position;
        }
        
        /// <summary>
        /// Sets the rotation of this Transform component.
        /// </summary>
        /// <param name="rotation">The amount of rotation in radians.</param>
        private void SetRotation(Single rotation)
        {
            if (GameObject.RigidBody != null)
                GameObject.RigidBody.Body.Rotation = rotation;

            mRotation = rotation;
        }

        /// <summary>
        /// Traverses the parent-child hierarchy until it finds the root Transform component.
        /// </summary>
        /// <returns>The found Transform.</returns>
        private Transform GetRootTransform()
        {
            Transform foundTransform = this.Parent;

            while (foundTransform != null)
            {
                if (foundTransform.Parent == null)
                    return foundTransform;

                foundTransform = foundTransform.Parent;                
            }
            return null;
        }

        private Rectangle CalculateBoundingRectangle(Rectangle rectangle)
        {
            Matrix matrix =
                Matrix.CreateTranslation(new Vector3(-Renderer.Origin, 0.0f)) *
                Matrix.CreateRotationZ(Transform.RotationRadians) *
                Matrix.CreateTranslation(new Vector3(Transform.Position, 0.0f));

            // Get all four corners in local space
            Vector2 leftTop = new Vector2(rectangle.Left, rectangle.Top);
            Vector2 rightTop = new Vector2(rectangle.Right, rectangle.Top);
            Vector2 leftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
            Vector2 rightBottom = new Vector2(rectangle.Right, rectangle.Bottom);

            // Transform all four corners into work space
            Vector2.Transform(ref leftTop, ref matrix, out leftTop);
            Vector2.Transform(ref rightTop, ref matrix, out rightTop);
            Vector2.Transform(ref leftBottom, ref matrix, out leftBottom);
            Vector2.Transform(ref rightBottom, ref matrix, out rightBottom);

            // Find the minimum and maximum extents of the rectangle in world space
            Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop),
                                      Vector2.Min(leftBottom, rightBottom));
            Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop),
                                      Vector2.Max(leftBottom, rightBottom));

            // Return as a rectangle
            return new Rectangle((int)min.X, (int)min.Y,
                                 (int)(max.X - min.X), (int)(max.Y - min.Y));
        }
    }
}
